import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Created by Administrator on 2016/4/1.
 */
public class E29_ToastOMatic {
    public static void main(String[] args) throws InterruptedException {
        ToastQueue
                dryQueue = new ToastQueue(),
                butteredQueue = new ToastQueue(),
                toBeButteredQueue = new ToastQueue(),
                jammedQueue = new ToastQueue(),
                toBeJammedQueue = new ToastQueue(),
                finishedQueue = new ToastQueue();
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Toaster(dryQueue));
        exec.execute(new Butterer(toBeButteredQueue, butteredQueue));
        exec.execute(new Jammer(toBeJammedQueue, jammedQueue));
        exec.execute(new Eater(finishedQueue));
        exec.execute(new Alternator(dryQueue, toBeButteredQueue, toBeJammedQueue));
        exec.execute(new Merger(butteredQueue, jammedQueue, toBeButteredQueue, toBeJammedQueue, finishedQueue));
        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}

class Toast {
    public enum Status {
        DRY, BUTTERED, JAMMED,
        READY {
            @Override
            public String toString() {
                return BUTTERED.toString() + " & " + JAMMED.toString();
            }
        }
    }

    private Status status = Status.DRY;
    private final int id;

    public Toast(int id) {
        this.id = id;
    }

    public void butter() {
        status = status == Status.DRY ? Status.BUTTERED : Status.READY;
    }

    public void jam() {
        status = status == Status.DRY ? Status.JAMMED : Status.READY;
    }

    public Status getStatus() {
        return status;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Toast " + id + ": " + status;
    }
}

class ToastQueue extends LinkedBlockingQueue<Toast> {
}

class Toaster implements Runnable {
    private ToastQueue toastQueue;
    private int count;
    private Random rand = new Random(47);

    public Toaster(ToastQueue toastQueue) {
        this.toastQueue = toastQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                TimeUnit.MILLISECONDS.sleep(100 + rand.nextInt(500));
                Toast toast = new Toast(count++);
                System.out.println(toast);
                toastQueue.put(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Toaster interrupted");
        }
        System.out.println("Toaster off");
    }
}

class Butterer implements Runnable {
    private ToastQueue inQueue, butteredQueue;

    public Butterer(ToastQueue inQueue, ToastQueue butteredQueue) {
        this.butteredQueue = butteredQueue;
        this.inQueue = inQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = inQueue.take();
                toast.butter();
                System.out.println(toast);
                butteredQueue.put(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Butterer interrupted");
        }
        System.out.println("Butterer off");
    }
}

class Jammer implements Runnable {
    private ToastQueue inQueue, jammerQueue;

    public Jammer(ToastQueue inQueue, ToastQueue jammerQueue) {
        this.jammerQueue = jammerQueue;
        this.inQueue = inQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = inQueue.take();
                toast.jam();
                System.out.println(toast);
                jammerQueue.put(toast);
            }
        } catch (InterruptedException e) {
            System.out.println("Jammer interrupted");
        }
        System.out.println("Jammer off");
    }
}

class Eater implements Runnable {
    private ToastQueue finishedQueue;
    private int counter = 0;

    public Eater(ToastQueue finishedQueue) {
        this.finishedQueue = finishedQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = finishedQueue.take();

                if (toast.getId() != counter++ || toast.getStatus() != Toast.Status.READY) {
                    System.out.println(">>>> Error: " + toast);
                    System.exit(1);
                } else {
                    System.out.println("Chomp! " + toast);
                }
            }
        } catch (InterruptedException e) {
            System.out.println("Eater interrupted");
        }
        System.out.println("Eater off");
    }
}

class Alternator implements Runnable {
    private ToastQueue inQueue, out1Queue, out2Queue;
    private boolean outTo2;

    public Alternator(ToastQueue inQueue, ToastQueue out1Queue, ToastQueue out2Queue) {
        this.inQueue = inQueue;
        this.out1Queue = out1Queue;
        this.out2Queue = out2Queue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = inQueue.take();
                if (!outTo2) {
                    out1Queue.put(toast);
                } else {
                    out2Queue.put(toast);
                }
                outTo2 = !outTo2;
            }
        } catch (InterruptedException e) {
            System.out.println("Alternator interrupted");
        }
        System.out.println("Alternator off");
    }
}

class Merger implements Runnable {
    private ToastQueue in1Queue, in2Queue, toBeButteredQueue,
            toBeJammedQueue, finishedQueue;

    public Merger(ToastQueue in1Queue, ToastQueue in2Queue, ToastQueue toBeButteredQueue, ToastQueue toBeJammedQueue, ToastQueue finishedQueue) {
        this.in1Queue = in1Queue;
        this.in2Queue = in2Queue;
        this.toBeButteredQueue = toBeButteredQueue;
        this.toBeJammedQueue = toBeJammedQueue;
        this.finishedQueue = finishedQueue;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Toast toast = null;
                while (toast == null) {
                    toast = in1Queue.poll(50, TimeUnit.MILLISECONDS);
                    if (toast != null) {
                        break;
                    }
                    toast = in2Queue.poll(50, TimeUnit.MILLISECONDS);
                }

                // Relay toast onto the proper queue
                switch (toast.getStatus()) {
                    case BUTTERED:
                        toBeJammedQueue.put(toast);
                        break;
                    case JAMMED:
                        toBeJammedQueue.put(toast);
                        break;
                    default:
                        finishedQueue.put(toast);
                        break;
                }
            }

        } catch (InterruptedException e) {
            System.out.println("Merger interrupted");
        }
        System.out.println("Merger off");
    }
}